{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "339bcc08-bb29-4c42-bfef-adf04464cf33",
   "metadata": {},
   "source": [
    "### 1.载入数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "89542841-26ea-41b2-82f4-913057f13e45",
   "metadata": {},
   "outputs": [],
   "source": [
    "import jieba"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2f2689db-b3a9-4031-9a60-b40f75e0609d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n",
      "Loading model from cache C:\\Users\\HP\\AppData\\Local\\Temp\\jieba.cache\n",
      "Loading model cost 0.503 seconds.\n",
      "Prefix dict has been built successfully.\n"
     ]
    }
   ],
   "source": [
    "academy_titles = []\n",
    "job_titles = []\n",
    "with open(\"academy_titles.txt\", encoding=\"utf-8\", mode=\"r\") as f:\n",
    "    for line in f:\n",
    "        academy_titles.append(list(jieba.cut(line.strip())))\n",
    "\n",
    "with open(\"job_titles.txt\", encoding=\"utf-8\", mode=\"r\") as f:\n",
    "    for line in f:\n",
    "        job_titles.append(list(jieba.cut(line.strip())))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "4db4bf3e-4e1b-4177-807f-d4089ce6a331",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[['北', '师', '教育学', '，', '你', '我', '一起', '努力', '，', '让', '胜利', '酣畅淋漓', '。'],\n",
       " ['考博', '英语词汇'],\n",
       " ['出售', '人大', '新闻', '学院', '2015', '年', '考研', '权威', '资料']]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "academy_titles[:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "235004a3-cc75-4c4a-a2dd-42504e490eda",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[['【',\n",
       "  '字节',\n",
       "  '跳动',\n",
       "  '内',\n",
       "  '推',\n",
       "  '】',\n",
       "  '校招',\n",
       "  '岗位',\n",
       "  '全面',\n",
       "  '开放',\n",
       "  '，',\n",
       "  '帮查',\n",
       "  '进度',\n",
       "  '！'],\n",
       " ['招聘', '兼职', '/', ' ', '笔试', '考务', ' ', '/', '200', '-', '300', ' ', '每人'],\n",
       " ['国企', '出版社', '招聘', '坐班', '兼职', '生']]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "job_titles[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bffedaf-a9f8-4541-9fc4-06cd62604f92",
   "metadata": {},
   "source": [
    "### 2.分词统计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "06214b9f-02bc-490a-809c-dd84a23ee944",
   "metadata": {},
   "outputs": [],
   "source": [
    "word_set = set()\n",
    "\n",
    "for title in academy_titles:\n",
    "    for word in title:\n",
    "        word_set.add(word)\n",
    "\n",
    "for title in job_titles:\n",
    "    for word in title:\n",
    "        word_set.add(word)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "10459d34-54f6-4847-9bad-4335e371a4fd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4085\n"
     ]
    }
   ],
   "source": [
    "print(len(word_set))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "ce1e8345-ec9d-4acf-8f66-5b4bd0475d8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "word_list = list(word_set)\n",
    "n_words = len(word_list) + 1  #添加<UNK>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f20317a-74d6-4f5e-8884-c8333290b1da",
   "metadata": {},
   "source": [
    "### 3.词嵌入表示"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4fe938eb-ffbe-46ae-bef6-fad84e348408",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b334fc4e-a046-40f8-bd3c-6a380f077005",
   "metadata": {},
   "outputs": [],
   "source": [
    "def title_to_tensor(title):  #title 一条数据\n",
    "    tensor = torch.zeros(len(title), dtype = torch.long)  #tensor([0, 0, 0, ......, 0])\n",
    "    for li, word in enumerate(title):\n",
    "        try:\n",
    "            ind = word_list.index(word)  #获取word_list中单词对应的索引值\n",
    "        except ValueError:\n",
    "            ind = n_words - 1  #未找到单词，则用<UNK>索引代替\n",
    "        tensor[li] = ind  #替换句子中每个单词的索引值\n",
    "    return tensor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "110cd75f-5952-4895-b003-33719f61dafe",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['北', '师', '教育学', '，', '你', '我', '一起', '努力', '，', '让', '胜利', '酣畅淋漓', '。']\n"
     ]
    }
   ],
   "source": [
    "print(academy_titles[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "dc41b992-cf88-4d27-8bd2-83663d170e02",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1132,  976, 1976, 3756,  626, 3220, 2766, 1703, 3756, 2829, 2294, 1752,\n",
      "        1842])\n"
     ]
    }
   ],
   "source": [
    "print(title_to_tensor(academy_titles[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3f5a1a6-d889-4cd8-9b7b-e338198b81f1",
   "metadata": {},
   "source": [
    "### 4.定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "14bc550e-b7ce-499d-9fd9-8e8573062fb5",
   "metadata": {},
   "outputs": [],
   "source": [
    "#使用pytorch中自带的RNN模型， torch.nn.RNN定义模型\n",
    "import torch.nn as nn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "31e5f72c-490c-4e7c-b0a1-78492a9f57f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "class RNN(nn.Module):\n",
    "    def __init__(self, word_count, embedding_size, hidden_size, output_size):\n",
    "        super(RNN, self).__init__()\n",
    "        self.hidden_size = hidden_size\n",
    "        self.embedding = nn.Embedding(word_count, embedding_size)\n",
    "        self.rnn = nn.RNN(embedding_size, hidden_size, num_layers=1, bidirectional=False, batch_first=True)\n",
    "        self.cls = nn.Linear(hidden_size, output_size)\n",
    "        self.softmax = nn.LogSoftmax(dim=0)\n",
    "        \n",
    "    def forward(self, input_tensor):\n",
    "        word_vector = self.embedding(input_tensor)\n",
    "        output = self.rnn(word_vector)[0][0][len(input_tensor) - 1]\n",
    "        output = self.cls(output)\n",
    "        output = self.softmax(output)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "1a7d9938-f6b3-4aca-a7f0-41f1b35f243c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_rnn(rnn, input_tensor):\n",
    "    output = rnn(input_tensor.unsqueeze(dim=0))\n",
    "    return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "6018cb3f-ce76-4b5e-bfa6-92c590c05889",
   "metadata": {},
   "outputs": [],
   "source": [
    "def train(rnn, criterion, input_tensor, category_tensor):\n",
    "    rnn.zero_grad()\n",
    "    output = run_rnn(rnn, input_tensor)\n",
    "    loss = criterion(output.unsqueeze(dim=0), category_tensor)\n",
    "    loss.backward()\n",
    "    \n",
    "    for p in rnn.parameters():\n",
    "        p.data.add_(p.grad.data, alpha=-learning_rate)\n",
    "    return output, loss.item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "c3583cc0-af94-411b-95af-04621e125575",
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate(rnn, input_tensor):\n",
    "    with torch.no_grad():\n",
    "        output = run_rnn(rnn, input_tensor)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c7195ca-9bb4-4fad-b03f-742b167ae9c2",
   "metadata": {},
   "source": [
    "### 5.划分数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "78a942d3-fc48-4382-beaa-4f7a6b223a83",
   "metadata": {},
   "outputs": [],
   "source": [
    "all_data = []\n",
    "categories = [\"考研考博\", \"招聘信息\"]\n",
    "\n",
    "for l in academy_titles:\n",
    "    all_data.append((title_to_tensor(l), torch.tensor([0], dtype=torch.long)))\n",
    "\n",
    "for l in job_titles:\n",
    "    all_data.append((title_to_tensor(l), torch.tensor([1], dtype=torch.long)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "1fedbeaf-191c-4010-af8a-534e929f8fb9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train size: 4975\n",
      "test size: 2133\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "random.shuffle(all_data)\n",
    "data_len = len(all_data)\n",
    "split_ratio = 0.7\n",
    "train_data = all_data[:int(data_len * split_ratio)]\n",
    "test_data = all_data[int(data_len * split_ratio):]\n",
    "print(\"train size:\", len(train_data))\n",
    "print(\"test size:\", len(test_data))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "832702b7-3633-43c0-8e01-0e8331d2f0ce",
   "metadata": {},
   "source": [
    "### 6.模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "bd92c478-6981-43d7-a2a7-ae2089d7fedc",
   "metadata": {},
   "outputs": [],
   "source": [
    "#参数设置\n",
    "from tqdm import tqdm\n",
    "\n",
    "epoch = 1\n",
    "embedding_size = 200\n",
    "n_hidden = 10\n",
    "n_categories = 2\n",
    "learning_rate = 0.005\n",
    "rnn = RNN(n_words, embedding_size, n_hidden, n_categories)\n",
    "criterion = nn.NLLLoss()\n",
    "loss_sum = 0\n",
    "all_losses = []\n",
    "plot_every = 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "eb93fd3a-dc89-4115-9b31-1e6ebcc6f03e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|█████████████████████████████████████████████████████████████████████████████| 4975/4975 [00:28<00:00, 172.18it/s]\n",
      "100%|████████████████████████████████████████████████████████████████████████████| 2133/2133 [00:00<00:00, 3875.68it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "accuracy 0.9231129864041256\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "for e in range(epoch):\n",
    "    for ind, (title_tensor, label) in enumerate(tqdm(train_data)):\n",
    "               output, loss = train(rnn, criterion, title_tensor, label)\n",
    "               loss_sum += loss\n",
    "                \n",
    "               if ind % plot_every == 0:\n",
    "                   all_losses.append(loss_sum /plot_every)\n",
    "                   loss_sum = 0\n",
    "    c = 0\n",
    "    for title, category in tqdm(test_data):\n",
    "        output = evaluate(rnn, title)\n",
    "        topn, topi = output.topk(1)\n",
    "        if topi.item() == category[0].item():\n",
    "            c += 1\n",
    "    print('accuracy', c / len(test_data))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9c6c6c3-7f47-4f55-9608-98941079d3cf",
   "metadata": {},
   "source": [
    "### 7.模型保存与加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "fcb53257-846f-4887-86aa-50144e3f6575",
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.save(rnn, \"rnn_model.pkl\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "29e706e4-739e-499b-8c89-99620be9de6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "rnn = torch.load(\"rnn_model.pkl\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2986ef57-97eb-49dd-9501-9c894593ca6c",
   "metadata": {},
   "source": [
    "### 8.测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "60fbc62f-69e7-4eb4-86ce-90bf2dad7b5f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_category(title):\n",
    "    title = title_to_tensor(title)\n",
    "    output = evaluate(rnn, title)\n",
    "    topn, topi = output.topk(1)\n",
    "    return categories[topi.item()]\n",
    "def print_test(title):\n",
    "    print((title, get_category(title)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "c5df4f14-35d9-4d26-b926-e92dc3f69788",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('考研心得', '考研考博')\n",
      "('北大实验室博士', '考研考博')\n",
      "('校外博士招考', '考研考博')\n",
      "('急求自然语言处理工程师', '招聘信息')\n",
      "('校招offer比较', '考研考博')\n",
      "('工作还是考研', '招聘信息')\n",
      "('工作吧', '招聘信息')\n",
      "('招聘人员', '招聘信息')\n"
     ]
    }
   ],
   "source": [
    "print_test(\"考研心得\")\n",
    "print_test(\"北大实验室博士\")\n",
    "print_test(\"校外博士招考\")\n",
    "print_test(\"急求自然语言处理工程师\")\n",
    "print_test(\"校招offer比较\")\n",
    "print_test(\"工作还是考研\")\n",
    "print_test(\"工作吧\")\n",
    "print_test(\"招聘人员\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0fbbf499-7943-4518-98a0-91c269826140",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
